home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / chrome / calendar.jar / content / calendar / sun-calendar-event-dialog-attendees.xml < prev    next >
Extensible Markup Language  |  2008-01-16  |  72KB  |  1,838 lines

  1. <?xml version="1.0"?>
  2. <!-- ***** BEGIN LICENSE BLOCK *****
  3.    - Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.    -
  5.    - The contents of this file are subject to the Mozilla Public License Version
  6.    - 1.1 (the "License"); you may not use this file except in compliance with
  7.    - the License. You may obtain a copy of the License at
  8.    - http://www.mozilla.org/MPL/
  9.    -
  10.    - Software distributed under the License is distributed on an "AS IS" basis,
  11.    - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.    - for the specific language governing rights and limitations under the
  13.    - License.
  14.    -
  15.    - The Original Code is Sun Microsystems code.
  16.    -
  17.    - The Initial Developer of the Original Code is Sun Microsystems.
  18.    - Portions created by the Initial Developer are Copyright (C) 2006
  19.    - the Initial Developer. All Rights Reserved.
  20.    -
  21.    - Contributor(s):
  22.    -   Michael Buettner <michael.buettner@sun.com>
  23.    -   Philipp Kewisch <mozilla@kewis.ch>
  24.    -
  25.    - Alternatively, the contents of this file may be used under the terms of
  26.    - either the GNU General Public License Version 2 or later (the "GPL"), or
  27.    - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.    - in which case the provisions of the GPL or the LGPL are applicable instead
  29.    - of those above. If you wish to allow use of your version of this file only
  30.    - under the terms of either the GPL or the LGPL, and not to allow others to
  31.    - use your version of this file under the terms of the MPL, indicate your
  32.    - decision by deleting the provisions above and replace them with the notice
  33.    - and other provisions required by the GPL or the LGPL. If you do not delete
  34.    - the provisions above, a recipient may use your version of this file under
  35.    - the terms of any one of the MPL, the GPL or the LGPL.
  36.    -
  37.    - ***** END LICENSE BLOCK ***** -->
  38.  
  39. <!DOCTYPE dialog [
  40.   <!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
  41.   <!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd2;
  42.   <!ENTITY % dtd3 SYSTEM "chrome://calendar/locale/sun-calendar-event-dialog.dtd" > %dtd3;
  43.   <!ENTITY % calendar-event-dialogDTD SYSTEM "chrome://calendar/locale/calendar-event-dialog.dtd"> %calendar-event-dialogDTD;
  44. ]>
  45.  
  46. <bindings xmlns="http://www.mozilla.org/xbl"
  47.           xmlns:xbl="http://www.mozilla.org/xbl"
  48.           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  49.   <binding id="attendees-list">
  50.     <content>
  51.       <xul:listbox anonid="listbox" seltype="multiple" rows="-1" flex="1">
  52.         <xul:listcols>
  53.           <xul:listcol/>
  54.           <xul:listcol flex="1"/>
  55.         </xul:listcols>
  56.         <xul:listitem anonid="item" class="addressingWidgetItem" allowevents="true">
  57.           <xul:listcell class="addressingWidgetCell" align="center" pack="center">
  58.             <xul:image id="attendeeCol1#1" anonid="icon"/>
  59.           </xul:listcell>
  60.           <xul:listcell class="addressingWidgetCell">
  61.             <xul:textbox id="attendeeCol2#1"
  62.                          anonid="input"
  63.                          class="plain textbox-addressingWidget uri-element"
  64.                          type="autocomplete"
  65.                          flex="1"
  66.                          searchSessions="addrbook"
  67.                          timeout="300"
  68.                          maxrows="4"
  69.                          autoFill="true"
  70.                          autoFillAfterMatch="true"
  71.                          forceComplete="true"
  72.                          minResultsForPopup="1"
  73.                          ignoreBlurWhileSearching="true"
  74.                          oninput="this.setAttribute('dirty', 'true');">
  75.               <xul:image class="person-icon" onclick="this.parentNode.select();"/>
  76.             </xul:textbox>
  77.           </xul:listcell>
  78.         </xul:listitem>
  79.       </xul:listbox>
  80.     </content>
  81.  
  82.     <implementation>
  83.       <field name="mMaxAttendees">0</field>
  84.       <field name="mContentHeight">0</field>
  85.       <field name="mRowHeight">0</field>
  86.       <field name="mNumColumns">0</field>
  87.       <field name="mIOService">null</field>
  88.       <field name="mDirectoryServerObserver">null</field>
  89.       <field name="mHeaderParser">null</field>
  90.       <field name="mPrefs">null</field>
  91.       <field name="mIsOffline">0</field>
  92.       <field name="mLDAPSession">null</field>
  93.       <field name="mSessionAdded">0</field>
  94.       <field name="mOrganizerID">null</field>
  95.       <field name="mIsReadOnly">false</field>
  96.       <field name="mIsInvitation">false</field>
  97.       <field name="mPopupOpen">false</field>
  98.  
  99.       <constructor><![CDATA[
  100.         this.mMaxAttendees = 0;
  101.  
  102.         var self = this;
  103.         var load = function loadHandler() {
  104.             self.onLoad();
  105.         };
  106.         window.addEventListener("load", load, true);
  107.         var unload = function attendeeListBinding_unloadHandler() {
  108.             self.onUnload();
  109.         };
  110.         window.addEventListener("unload", unload, true);
  111.  
  112.         var observer = {
  113.             observe: function DSO_observe(subject,
  114.                               topic,
  115.                               value) {
  116.                 // catch the exception and ignore it, so that if LDAP setup
  117.                 // fails, the entire window doesn't get horked
  118.                 try {
  119.                     self.setupAutocomplete();
  120.                 }
  121.                 catch (ex) {
  122.                 }
  123.             }
  124.         }
  125.  
  126.         this.mDirectoryServerObserver = observer;
  127.  
  128.         var component = Components.classes["@mozilla.org/messenger/headerparser;1"];
  129.         if (component) {
  130.             this.mHeaderParser = component.getService(Components.interfaces.nsIMsgHeaderParser);
  131.         }
  132.  
  133.         // First get the preferences service
  134.         try {
  135.             this.mPrefs = Components.classes["@mozilla.org/preferences-service;1"]
  136.                                     .getService(Components.interfaces.nsIPrefService)
  137.                                     .getBranch(null);
  138.             this.mPrefs = this.mPrefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
  139.         } catch (ex) {
  140.         }
  141.  
  142.         this.mIOService = Components.classes["@mozilla.org/network/io-service;1"]
  143.                                     .getService(Components.interfaces.nsIIOService);
  144.         this.mIsOffline = this.mIOService.offline;
  145.       ]]></constructor>
  146.  
  147.       <method name="onLoad">
  148.         <body><![CDATA[
  149.           var listbox =
  150.               document.getAnonymousElementByAttribute(
  151.                   this, "anonid", "listbox");
  152.           var template =
  153.               document.getAnonymousElementByAttribute(
  154.                   this, "anonid", "item");
  155.  
  156.           // we need to enforce several layout constraints which can't be modelled
  157.           // with plain xul and css, at least as far as i know.
  158.           const kStylesheet = "chrome://calendar/content/sun-calendar-event-dialog.css";
  159.           for each (var stylesheet in document.styleSheets) {
  160.               if (stylesheet.href == kStylesheet) {
  161.                   // the height of the text blocks contained in the grid items needs
  162.                   // to have the same height as the items of the attendee-list.
  163.                   var height = template.boxObject.height - 1;
  164.                   stylesheet.insertRule(".freebusy-grid { min-height: " + height + "px; }", 0);
  165.                   break;
  166.               }
  167.           }
  168.  
  169.           this.onInitialize();
  170.  
  171.           // this trigger the continous update chain, which
  172.           // effectively calls this.onModify() on predefined
  173.           // time intervals [each second].
  174.           var self = this;
  175.           var callback = function() {
  176.               setTimeout(callback, 1000);
  177.               self.onModify();
  178.           }
  179.           callback();
  180.         ]]></body>
  181.       </method>
  182.  
  183.       <method name="onInitialize">
  184.         <body><![CDATA[
  185.           var args = window.arguments[0];
  186.           var organizer = args.organizer;
  187.           var attendees = args.attendees;
  188.           var calendar = args.calendar;
  189.  
  190.           // set 'mIsReadOnly' if the calendar is read-only
  191.           if (calendar && calendar.readOnly) {
  192.               this.mIsReadOnly = true;
  193.           }
  194.  
  195.           // assume we're the organizer [in case that the calendar
  196.           // does not support the concept of identities].
  197.           this.mIsInvitation = false;
  198.           this.mOrganizerID = ((organizer && organizer.id) ? organizer.id : "");
  199.           try {
  200.               // temporary hack unless all group scheduling features are supported
  201.               // by the caching facade (calCachedCalendar):
  202.               var provider = calendar.getProperty("private.wcapCalendar")
  203.                                      .QueryInterface(Components.interfaces.calIWcapCalendar);
  204.               this.mIsInvitation = provider.isInvitation(args.item);
  205.               if (this.mOrganizerID.length == 0) {
  206.                   this.mOrganizerID = provider.ownerId; // sensible default
  207.               }
  208.           }
  209.           catch (e) {
  210.           }
  211.  
  212.           var listbox =
  213.               document.getAnonymousElementByAttribute(
  214.                   this, "anonid", "listbox");
  215.           var template =
  216.               document.getAnonymousElementByAttribute(
  217.                   this, "anonid", "item");
  218.           template.focus();
  219.  
  220.           if (this.mIsReadOnly || this.mIsInvitation) {
  221.               listbox.setAttribute("disabled", "true");
  222.           }
  223.  
  224.           // TODO: the organizer should show up in the attendee list, but this information
  225.           // should be based on the organizer contained in the appropriate field of calIItemBase.
  226.           // This is currently not supported, since we're still missing calendar identities.
  227.           if (this.mOrganizerID && this.mOrganizerID != "") {
  228.               if (!organizer) {
  229.                   organizer = this.createAttendee();
  230.                   organizer.id = this.mOrganizerID;
  231.                   organizer.role = "CHAIR";
  232.                   organizer.participationStatus = "ACCEPTED";
  233.               } else {
  234.                   if (!organizer.id) {
  235.                       organizer.id = this.mOrganizerID;
  236.                   }
  237.                   if (!organizer.role) {
  238.                       organizer.role = "CHAIR";
  239.                   }
  240.                   if (!organizer.participationStatus) {
  241.                       organizer.participationStatus = "ACCEPTED";
  242.                   }
  243.               }
  244.               try {
  245.                   // temporary hack unless all group scheduling features are supported
  246.                   // by the caching facade (calCachedCalendar):
  247.                   var provider = calendar.getProperty("private.wcapCalendar")
  248.                                          .QueryInterface(Components.interfaces.calIWcapCalendar);
  249.                   var props = provider.getCalendarProperties("X-S1CS-CALPROPS-COMMON-NAME", {});
  250.                   if (props.length > 0) {
  251.                       if(organizer.commonName.length <= 0) {
  252.                          organizer.commonName = props[0];
  253.                       }
  254.                   }
  255.               }
  256.               catch (e) {
  257.               }
  258.               this.appendAttendee(organizer, listbox, template, true);
  259.           }
  260.  
  261.           var numRowsAdded = 0;
  262.           if (attendees.length > 0) {
  263.               for each (var attendee in attendees) {
  264.                   this.appendAttendee(attendee, listbox, template, false);
  265.                   numRowsAdded++;
  266.               }
  267.           }
  268.           if (numRowsAdded == 0) {
  269.               this.appendAttendee(null, listbox, template, false);
  270.           }
  271.  
  272.           // detach the template item from the listbox, but hold the reference.
  273.           // until this function returns we add at least a single copy of this template back again.
  274.           listbox.removeChild(template);
  275.  
  276.           this.addDirectoryServerObserver();
  277.  
  278.           this.setFocus(this.mMaxAttendees);
  279.         ]]></body>
  280.       </method>
  281.  
  282.       <method name="onUnload">
  283.         <body><![CDATA[
  284.           this.removeDirectoryServerObserver();
  285.           this.releaseAutoCompleteState();
  286.           this.mIOService = null;
  287.           this.mLDAPSession = null;
  288.         ]]></body>
  289.       </method>
  290.  
  291.       <!-- appends a new row using an existing attendee structure -->
  292.       <method name="appendAttendee">
  293.         <parameter name="aAttendee"/>
  294.         <parameter name="aParentNode"/>
  295.         <parameter name="aTemplateNode"/>
  296.         <parameter name="aDisableIfOrganizer"/>
  297.         <body><![CDATA[
  298.           // create a new listbox item and append it to our parent control.
  299.           var newNode = aTemplateNode.cloneNode(true);
  300.  
  301.           var input =
  302.               document.getAnonymousElementByAttribute(
  303.                   newNode, "anonid", "input");
  304.           var icon =
  305.               document.getAnonymousElementByAttribute(
  306.                   newNode, "anonid", "icon");
  307.  
  308.           // We always clone the first row. The problem is that the first row
  309.           // could be focused. When we clone that row, we end up with a cloned
  310.           // XUL textbox that has a focused attribute set.  Therefore we think
  311.           // we're focused and don't properly refocus.  The best solution to this
  312.           // would be to clone a template row that didn't really have any presentation,
  313.           // rather than using the real visible first row of the listbox.
  314.           // For now we'll just put in a hack that ensures the focused attribute
  315.           // is never copied when the node is cloned.
  316.           if (input.getAttribute('focused') != '') {
  317.               input.removeAttribute('focused');
  318.           }
  319.  
  320.           aParentNode.appendChild(newNode);
  321.  
  322.           // the template could have its fields disabled,
  323.           // that's why we need to reset their status.
  324.           input.removeAttribute("disabled");
  325.           icon.removeAttribute("disabled");
  326.  
  327.           if (this.mIsReadOnly || this.mIsInvitation) {
  328.               input.setAttribute("disabled", "true");
  329.               icon.setAttribute("disabled", "true");
  330.           }
  331.  
  332.           // disable the input-field [name <email>] if this attendee
  333.           // appears to be the organizer.
  334.           if (aDisableIfOrganizer) {
  335.               if (aAttendee) {
  336.                   if (this.mOrganizerID && this.mOrganizerID != "") {
  337.                       if (aAttendee.id.toLowerCase() == this.mOrganizerID.toLowerCase()) {
  338.                           input.setAttribute("disabled", "true");
  339.                       }
  340.                   }
  341.               }
  342.           }
  343.  
  344.           this.mMaxAttendees++;
  345.           var rowNumber = this.mMaxAttendees;
  346.           if (rowNumber >= 0) {
  347.               icon.setAttribute("id", "attendeeCol1#" + rowNumber);
  348.               input.setAttribute("id", "attendeeCol2#" + rowNumber);
  349.           }
  350.  
  351.           if (!aAttendee) {
  352.               aAttendee = this.createAttendee();
  353.           }
  354.  
  355.           // construct the display string from common name and/or email address.
  356.           var inputValue = aAttendee.commonName;
  357.           var regexp = new RegExp("^mailto:(.*)", "i");
  358.           if (inputValue && this.mHeaderParser) {
  359.               var email = aAttendee.id;
  360.               if (email && email.length) {
  361.                   if (regexp.test(email)) {
  362.                       inputValue += ' <' + RegExp.$1 + '>';
  363.                   } else {
  364.                       inputValue += ' <' + email + '>';
  365.                   }
  366.               }
  367.           } else {
  368.               var email = aAttendee.id;
  369.               if (email && email.length) {
  370.                   if (regexp.test(email)) {
  371.                       inputValue = RegExp.$1;
  372.                   } else {
  373.                       inputValue = email;
  374.                   }
  375.               }
  376.           }
  377.  
  378.           // remove leading spaces
  379.           while (inputValue && inputValue[0] == " " ) {
  380.               inputValue = inputValue.substring(1, inputValue.length);
  381.           }
  382.  
  383.           input.setAttribute("value", inputValue);
  384.           input.value = inputValue;
  385.           input.attendee = aAttendee;
  386.           input.setAttribute("dirty", "true");
  387.  
  388.           if (aAttendee) {
  389.               if (this.mOrganizerID && this.mOrganizerID != "") {
  390.                   if (aAttendee.id.toLowerCase() == this.mOrganizerID.toLowerCase()) {
  391.                       icon.setAttribute("class", "status-icon");
  392.                       icon.setAttribute("status", aAttendee.participationStatus);
  393.                       return true;
  394.                   }
  395.               }
  396.           }
  397.  
  398.           icon.setAttribute("class", "role-icon");
  399.           icon.setAttribute("role", aAttendee.role);
  400.  
  401.           return true;
  402.         ]]></body>
  403.       </method>
  404.  
  405.       <method name="appendNewRow">
  406.         <parameter name="aSetFocus"/>
  407.         <body><![CDATA[
  408.           var listbox =
  409.               document.getAnonymousElementByAttribute(
  410.                   this, "anonid", "listbox");
  411.           var listitem1 = this.getListItem(1);
  412.  
  413.           if (listbox && listitem1) {
  414.               var newAttendee = this.createAttendee();
  415.               var nextDummy = this.getNextDummyRow();
  416.               var newNode = listitem1.cloneNode(true);
  417.  
  418.               var input =
  419.                   document.getAnonymousElementByAttribute(
  420.                       newNode, "anonid", "input");
  421.               var icon =
  422.                   document.getAnonymousElementByAttribute(
  423.                       newNode, "anonid", "icon");
  424.  
  425.               // the template could have its fields disabled,
  426.               // that's why we need to reset their status.
  427.               input.removeAttribute("disabled");
  428.               icon.removeAttribute("disabled");
  429.  
  430.               if (this.mIsReadOnly || this.mIsInvitation) {
  431.                   input.setAttribute("disabled", "true");
  432.                   icon.setAttribute("disabled", "true");
  433.               }
  434.  
  435.               this.mMaxAttendees++;
  436.               var rowNumber = this.mMaxAttendees;
  437.               if (rowNumber >= 0) {
  438.                   icon.setAttribute("id", "attendeeCol1#" + rowNumber);
  439.                   input.setAttribute("id", "attendeeCol2#" + rowNumber);
  440.               }
  441.  
  442.               input.value = null;
  443.               input.removeAttribute("value");
  444.               input.attendee = newAttendee;
  445.  
  446.               // this copies the autocomplete sessions list from first attendee
  447.               if (this.mLDAPSession && this.mSessionAdded) {
  448.                   input.syncSessions(document.getElementById('attendeeCol2#1'));
  449.               }
  450.  
  451.               // set role and participation status
  452.               //status.setAttribute("status", newAttendee.participationStatus);
  453.               //role.setAttribute("role", newAttendee.role);
  454.               icon.setAttribute("class", "role-icon");
  455.               icon.setAttribute("role", "REQ-PARTICIPANT");
  456.  
  457.               // We always clone the first row.  The problem is that the first row
  458.               // could be focused.  When we clone that row, we end up with a cloned
  459.               // XUL textbox that has a focused attribute set.  Therefore we think
  460.               // we're focused and don't properly refocus.  The best solution to this
  461.               // would be to clone a template row that didn't really have any presentation,
  462.               // rather than using the real visible first row of the listbox.
  463.               // For now we'll just put in a hack that ensures the focused attribute
  464.               // is never copied when the node is cloned.
  465.               if (input.getAttribute('focused') != '') {
  466.                   input.removeAttribute('focused');
  467.               }
  468.  
  469.               if (nextDummy) {
  470.                   listbox.replaceChild(newNode, nextDummy);
  471.               } else {
  472.                   listbox.appendChild(newNode);
  473.               }
  474.  
  475.               // focus on new input widget
  476.               if (aSetFocus) {
  477.                   this.setFocus(this.mMaxAttendees);
  478.               }
  479.  
  480.               this.onModify();
  481.           }
  482.         ]]></body>
  483.       </method>
  484.  
  485.       <property name="attendees">
  486.         <getter><![CDATA[
  487.           var attendees = [];
  488.  
  489.           var inputField;
  490.           for (var i = 1; inputField = this.getInputElement(i); i++) {
  491.               var fieldValue = inputField.value;
  492.               if (fieldValue != "") {
  493.                   // the inputfield already has a reference to the attendee
  494.                   // object, we just need to fill in the name.
  495.                   var attendee = inputField.attendee.clone();
  496.  
  497.                   attendee.role = this.getRoleElement(i).getAttribute("role");
  498.                   //attendee.participationStatus = this.getStatusElement(i).getAttribute("status");
  499.  
  500.                   // break the list of potentially many attendees back into individual names
  501.                   var emailAddresses = {};
  502.                   var names = {};
  503.                   var fullNames = {};
  504.  
  505.                   if (this.mHeaderParser) {
  506.                       this.mHeaderParser.parseHeadersWithArray(fieldValue,
  507.                                                                emailAddresses,
  508.                                                                names,
  509.                                                                fullNames);
  510.                   } else {
  511.                       emailAddresses.value = [ fieldValue ];
  512.                       names.value = [];
  513.                   }
  514.  
  515.                   if (emailAddresses.value.length > 0) {
  516.                       // If the new address has no 'mailto'-prefix but seems
  517.                       // to look like an email-address, we prepend the prefix.
  518.                       // This also allows for non-email-addresses.
  519.                       var email = emailAddresses.value[0];
  520.                       if (email.toLowerCase().indexOf("mailto:") != 0) {
  521.                           if (email.indexOf("@") >= 0) {
  522.                               email = "MAILTO:" + email;
  523.                           }
  524.                       }
  525.                       attendee.id = email;
  526.                   }
  527.                   if (names.value.length > 0) {
  528.                       attendee.commonName = names.value[0];
  529.                   }
  530.  
  531.                   var addAttendee = true;
  532.                   if (this.mOrganizerID &&
  533.                       this.mOrganizerID != "" &&
  534.                       attendee.id.toLowerCase() == this.mOrganizerID.toLowerCase() &&
  535.                       i == 1) {
  536.                       addAttendee = false;
  537.                   }
  538.  
  539.                   // append the attendee object to the list of attendees.
  540.                   if (addAttendee) {
  541.                       attendees.push(attendee);
  542.                   }
  543.               }
  544.           }
  545.  
  546.           return attendees;
  547.         ]]></getter>
  548.       </property>
  549.  
  550.       <property name="organizer">
  551.         <getter><![CDATA[
  552.           var inputField;
  553.           for (var i = 1; inputField = this.getInputElement(i); i++) {
  554.               var fieldValue = inputField.value;
  555.               if (fieldValue != "") {
  556.                   // The inputfield already has a reference to the attendee
  557.                   // object, we just need to fill in the name.
  558.                   var attendee = inputField.attendee.clone();
  559.  
  560.                   //attendee.role = this.getRoleElement(i).getAttribute("role");
  561.                   attendee.participationStatus = this.getStatusElement(i).getAttribute("status");
  562.  
  563.                   // break the list of potentially many attendees back into individual names
  564.                   var emailAddresses = {};
  565.                   var names = {};
  566.                   var fullNames = {};
  567.  
  568.                   if (this.mHeaderParser) {
  569.                       this.mHeaderParser.parseHeadersWithArray(fieldValue,
  570.                                                                emailAddresses,
  571.                                                                names,
  572.                                                                fullNames);
  573.                   } else {
  574.                       emailAddresses.value = [ fieldValue ];
  575.                       names.value = [];
  576.                   }
  577.  
  578.                   if (emailAddresses.value.length > 0) {
  579.                       // if the new address has no 'mailto'-prefix but seems
  580.                       // to look like an email-address, we prepend the prefix.
  581.                       // this also allows for non-email-addresses.
  582.                       var email = emailAddresses.value[0];
  583.                       if (email.toLowerCase().indexOf("mailto:") != 0) {
  584.                           if (email.indexOf("@") >= 0) {
  585.                               email = "MAILTO:" + email;
  586.                           }
  587.                       }
  588.                       attendee.id = email;
  589.                   }
  590.                   if (names.value.length > 0) {
  591.                       attendee.commonName = names.value[0];
  592.                   }
  593.  
  594.                   if (this.mOrganizerID && this.mOrganizerID != "" &&
  595.                       attendee.id.toLowerCase() == this.mOrganizerID.toLowerCase()) {
  596.                       return attendee;
  597.                   }
  598.               }
  599.           }
  600.  
  601.           return null;
  602.         ]]></getter>
  603.       </property>
  604.  
  605.       <method name="onModify">
  606.         <body><![CDATA[
  607.           var list = [];
  608.           for (var i = 1; i <= this.mMaxAttendees; i++) {
  609.               // retrieve the string from the appropriate row
  610.               var input = this.getInputElement(i);
  611.               var fieldValue = input.value;
  612.  
  613.               // parse the string to break this down to individual names and addresses
  614.               var email = "";
  615.               var emailAddresses = {};
  616.               var names = {};
  617.               var fullNames = {};
  618.  
  619.               if (this.mHeaderParser) {
  620.                   this.mHeaderParser.parseHeadersWithArray(
  621.                       fieldValue,
  622.                       emailAddresses,
  623.                       names,
  624.                       fullNames);
  625.               } else {
  626.                   emailAddresses.value = [ fieldValue ];
  627.                   names.value = [];
  628.               }
  629.  
  630.               if (emailAddresses.value.length > 0) {
  631.                   // if the new address has no 'mailto'-prefix but seems
  632.                   // to look like an email-address, we prepend the prefix.
  633.                   // this also allows for non-email-addresses.
  634.                   email = emailAddresses.value[0];
  635.                   if (email.toLowerCase().indexOf("mailto:") != 0) {
  636.                       if (email.indexOf("@") >= 0) {
  637.                           email = "MAILTO:" + email;
  638.                       }
  639.                   }
  640.               }
  641.  
  642.               var isdirty = false;
  643.               if (input.hasAttribute("dirty")) {
  644.                   isdirty = input.getAttribute("dirty");
  645.               }
  646.               input.removeAttribute("dirty");
  647.               var entry = {
  648.                   dirty: isdirty,
  649.                   calid: email
  650.               };
  651.               list.push(entry);
  652.           }
  653.  
  654.           var event = document.createEvent('Events');
  655.           event.initEvent('modify', true, false);
  656.           event.details = list;
  657.           this.dispatchEvent(event);
  658.         ]]></body>
  659.       </method>
  660.  
  661.       <property name="documentSize">
  662.         <getter><![CDATA[
  663.             return this.mRowHeight * this.mMaxAttendees;
  664.         ]]></getter>
  665.       </property>
  666.  
  667.       <method name="fitDummyRows">
  668.         <body><![CDATA[
  669.           var self = this;
  670.           var func = function attendees_list_fitDummyRows() {
  671.               self.calcContentHeight();
  672.               self.createOrRemoveDummyRows();
  673.           }
  674.           setTimeout(func, 0);
  675.         ]]></body>
  676.       </method>
  677.  
  678.       <method name="calcContentHeight">
  679.         <body><![CDATA[
  680.           var listbox =
  681.               document.getAnonymousElementByAttribute(
  682.                   this, "anonid", "listbox");
  683.           var items = listbox.getElementsByTagNameNS('*', 'listitem');
  684.           this.mContentHeight = 0;
  685.           if (items.length > 0) {
  686.               var i = 0;
  687.               do {
  688.                   this.mRowHeight = items[i].boxObject.height;
  689.                   ++i;
  690.               } while (i < items.length && !this.mRowHeight);
  691.               this.mContentHeight = this.mRowHeight * items.length;
  692.           }
  693.         ]]></body>
  694.       </method>
  695.  
  696.       <method name="createOrRemoveDummyRows">
  697.         <body><![CDATA[
  698.           var listbox =
  699.               document.getAnonymousElementByAttribute(
  700.                   this, "anonid", "listbox");
  701.           var listboxHeight = listbox.boxObject.height;
  702.  
  703.           // remove rows to remove scrollbar
  704.           var kids = listbox.childNodes;
  705.           for (var i = kids.length - 1; this.mContentHeight > listboxHeight && i >= 0; --i) {
  706.               if (kids[i].hasAttribute("_isDummyRow")) {
  707.                   this.mContentHeight -= this.mRowHeight;
  708.                   listbox.removeChild(kids[i]);
  709.               }
  710.           }
  711.  
  712.           // add rows to fill space
  713.           if (this.mRowHeight) {
  714.               while (this.mContentHeight + this.mRowHeight < listboxHeight) {
  715.                   this.createDummyItem(listbox);
  716.                   this.mContentHeight += this.mRowHeight;
  717.               }
  718.           }
  719.         ]]></body>
  720.       </method>
  721.  
  722.       <method name="createDummyCell">
  723.         <parameter name="aParent"/>
  724.         <body><![CDATA[
  725.           var cell = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "listcell");
  726.           cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
  727.           if (aParent) {
  728.               aParent.appendChild(cell);
  729.           }
  730.           return cell;
  731.         ]]></body>
  732.       </method>
  733.  
  734.       <method name="createDummyItem">
  735.         <parameter name="aParent"/>
  736.         <body><![CDATA[
  737.           var titem = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "listitem");
  738.           titem.setAttribute("_isDummyRow", "true");
  739.           titem.setAttribute("class", "dummy-row");
  740.           for (var i = this.numColumns; i > 0; i--) {
  741.               this.createDummyCell(titem);
  742.           }
  743.           if (aParent) {
  744.               aParent.appendChild(titem);
  745.           }
  746.           return titem;
  747.         ]]></body>
  748.       </method>
  749.  
  750.       <!-- gets the next row from the top down -->
  751.       <method name="getNextDummyRow">
  752.         <body><![CDATA[
  753.           var listbox =
  754.               document.getAnonymousElementByAttribute(
  755.                   this, "anonid", "listbox");
  756.           var kids = listbox.childNodes;
  757.           for (var i = 0; i < kids.length; ++i) {
  758.               if (kids[i].hasAttribute("_isDummyRow")) {
  759.                   return kids[i];
  760.               }
  761.           }
  762.           return null;
  763.         ]]></body>
  764.       </method>
  765.  
  766.       <!-- This method returns the <xul:listitem> at row numer 'aRow' -->
  767.       <method name="getListItem">
  768.         <parameter name="aRow"/>
  769.         <body><![CDATA[
  770.           var listbox =
  771.               document.getAnonymousElementByAttribute(
  772.                   this, "anonid", "listbox");
  773.           if (listbox && aRow > 0) {
  774.               var listitems = listbox.getElementsByTagNameNS('*', 'listitem');
  775.               if (listitems && listitems.length >= aRow) {
  776.                   return listitems[aRow - 1];
  777.               }
  778.           }
  779.           return 0;
  780.         ]]></body>
  781.       </method>
  782.  
  783.       <method name="getRowByInputElement">
  784.         <parameter name="aElement"/>
  785.         <body><![CDATA[
  786.           var row = 0;
  787.           while (aElement && aElement.localName != "listitem") {
  788.               aElement = aElement.parentNode;
  789.           }
  790.           if (aElement) {
  791.               while (aElement) {
  792.                   if (aElement.localName == "listitem") {
  793.                       ++row;
  794.                   }
  795.                   aElement = aElement.previousSibling;
  796.               }
  797.           }
  798.           return row;
  799.         ]]></body>
  800.       </method>
  801.  
  802.       <!-- This method returns the <xul:textbox> that contains
  803.            the name of the attendee at row number 'aRow' -->
  804.       <method name="getInputElement">
  805.         <parameter name="aRow"/>
  806.         <body><![CDATA[
  807.           return document.getElementById("attendeeCol2#" + aRow);
  808.         ]]></body>
  809.       </method>
  810.  
  811.       <method name="getRoleElement">
  812.         <parameter name="aRow"/>
  813.         <body><![CDATA[
  814.           return document.getElementById("attendeeCol1#" + aRow);
  815.         ]]></body>
  816.       </method>
  817.  
  818.       <method name="getStatusElement">
  819.         <parameter name="aRow"/>
  820.         <body><![CDATA[
  821.           return document.getElementById("attendeeCol1#" + aRow);
  822.         ]]></body>
  823.       </method>
  824.  
  825.       <method name="setFocus">
  826.         <parameter name="aRow"/>
  827.         <body><![CDATA[
  828.           var self = this;
  829.           var set_focus = function() {
  830.               // do we need to scroll in order to see the selected row?
  831.               var node = self.getListItem(aRow);
  832.               var listbox =
  833.                   document.getAnonymousElementByAttribute(
  834.                       self, "anonid", "listbox");
  835.               var firstVisibleRow = listbox.getIndexOfFirstVisibleRow();
  836.               var numOfVisibleRows = listbox.getNumberOfVisibleRows();
  837.               if (aRow <= firstVisibleRow) {
  838.                   listbox.scrollToIndex(aRow - 1);
  839.               } else {
  840.                   if (aRow - 1 >= (firstVisibleRow + numOfVisibleRows)) {
  841.                       listbox.scrollToIndex(aRow - numOfVisibleRows);
  842.                   }
  843.               }
  844.               var input =
  845.                   document.getAnonymousElementByAttribute(
  846.                       node, "anonid", "input");
  847.               input.focus();
  848.           }
  849.           setTimeout(set_focus, 0);
  850.         ]]></body>
  851.       </method>
  852.  
  853.       <property name="firstVisibleRow">
  854.         <getter><![CDATA[
  855.           var listbox =
  856.               document.getAnonymousElementByAttribute(
  857.                   this, "anonid", "listbox");
  858.           return listbox.getIndexOfFirstVisibleRow();
  859.         ]]></getter>
  860.       </property>
  861.  
  862.       <method name="createAttendee">
  863.         <body><![CDATA[
  864.           var attendee = createAttendee();
  865.           attendee.id = "";
  866.           attendee.rsvp = true;
  867.           attendee.role = "REQ-PARTICIPANT";
  868.           attendee.participationStatus = "NEEDS-ACTION";
  869.           return attendee;
  870.         ]]></body>
  871.       </method>
  872.  
  873.       <property name="numColumns">
  874.         <getter><![CDATA[
  875.           if (!this.mNumColumns) {
  876.               var listbox =
  877.                   document.getAnonymousElementByAttribute(
  878.                       this, "anonid", "listbox");
  879.               var listCols = listbox.getElementsByTagNameNS('*', 'listcol');
  880.               this.mNumColumns = listCols.length;
  881.               if (!this.mNumColumns) {
  882.                   this.mNumColumns = 1;
  883.               }
  884.           }
  885.           return this.mNumColumns;
  886.         ]]></getter>
  887.       </property>
  888.  
  889.       <property name="ratio">
  890.         <setter><![CDATA[
  891.           var listbox =
  892.               document.getAnonymousElementByAttribute(
  893.                   this, "anonid", "listbox");
  894.           var rowcount = listbox.getRowCount();
  895.           listbox.scrollToIndex(Math.floor(rowcount * val));
  896.           return val;
  897.         ]]></setter>
  898.       </property>
  899.  
  900.       <method name="arrowHit">
  901.         <parameter name="aElement"/>
  902.         <parameter name="aDirection"/>
  903.         <body><![CDATA[
  904.           var row = this.getRowByInputElement(aElement) + aDirection;
  905.           if (row) {
  906.               if (row > this.mMaxAttendees) {
  907.                   this.appendNewRow(true);
  908.               } else {
  909.                   var input = this.getInputElement(row);
  910.                   if (input.hasAttribute("disabled")) {
  911.                       return;
  912.                   }
  913.                   this.setFocus(row);
  914.               }
  915.               var event = document.createEvent('Events');
  916.               event.initEvent('rowchange', true, false);
  917.               event.details = row;
  918.               this.dispatchEvent(event);
  919.           }
  920.         ]]></body>
  921.       </method>
  922.  
  923.       <method name="deleteHit">
  924.         <parameter name="aElement"/>
  925.         <body><![CDATA[
  926.           // don't delete the row if it's the last one remaining
  927.           if (this.mMaxAttendees <= 1) {
  928.               return;
  929.           }
  930.  
  931.           var row = this.getRowByInputElement(aElement);
  932.           this.deleteRow(row);
  933.           if (row > 1) {
  934.               row = row - 1;
  935.           }
  936.           this.setFocus(row);
  937.           this.onModify();
  938.  
  939.           var event = document.createEvent('Events');
  940.           event.initEvent('rowchange', true, false);
  941.           event.details = row;
  942.           this.dispatchEvent(event);
  943.         ]]></body>
  944.       </method>
  945.  
  946.       <method name="deleteRow">
  947.         <parameter name="aRow"/>
  948.         <body><![CDATA[
  949.           // reset id's in order to not break the sequence
  950.           var maxAttendees = this.mMaxAttendees;
  951.           this.removeRow(aRow);
  952.           var numberOfCols = this.numColumns;
  953.           for (var row = aRow + 1; row <= maxAttendees; row++) {
  954.               for (var col = 1; col <= numberOfCols; col++) {
  955.                   var colID = "attendeeCol" + col + "#" + row;
  956.                   var elem = document.getElementById(colID);
  957.                   if (elem) {
  958.                       elem.setAttribute("id", "attendeeCol" + col + "#" + (row - 1));
  959.                   }
  960.               }
  961.           }
  962.         ]]></body>
  963.       </method>
  964.  
  965.       <method name="removeRow">
  966.         <parameter name="aRow"/>
  967.         <body><![CDATA[
  968.           var listbox =
  969.               document.getAnonymousElementByAttribute(
  970.                   this, "anonid", "listbox");
  971.           var nodeToRemove = this.getListItem(aRow);
  972.           nodeToRemove.parentNode.removeChild(nodeToRemove);
  973.           this.fitDummyRows();
  974.           this.mMaxAttendees--;
  975.         ]]></body>
  976.       </method>
  977.  
  978.       <!-- ############################################################################# -->
  979.       <!-- LDAP support                                                                  -->
  980.       <!-- ############################################################################# -->
  981.  
  982.       <method name="setupAutocomplete">
  983.         <body><![CDATA[
  984.           var autocompleteLdap = false;
  985.           var autocompleteDirectory = null;
  986.           var prevAutocompleteDirectory = this.mCurrentAutocompleteDirectory;
  987.           var i;
  988.  
  989.           try {
  990.               autocompleteLdap = this.mPrefs.getBoolPref("ldap_2.autoComplete.useDirectory");
  991.           } catch (ex) {
  992.               return;
  993.           }
  994.           if (autocompleteLdap) {
  995.               autocompleteDirectory = this.mPrefs.getCharPref(
  996.                   "ldap_2.autoComplete.directoryServer");
  997.           }
  998.  
  999.           // Use a temporary to do the setup so that we don't overwrite the
  1000.           // global, then have some problem and throw an exception, and leave the
  1001.           // global with a partially setup session.  We'll assign the temp
  1002.           // into the global after we're done setting up the session.
  1003.           var LDAPSession;
  1004.           if (this.mLDAPSession) {
  1005.               LDAPSession = this.mLDAPSession;
  1006.           } else {
  1007.               LDAPSession = Components.classes[
  1008.                   "@mozilla.org/autocompleteSession;1?type=ldap"].createInstance()
  1009.                   .QueryInterface(
  1010.                       Components.interfaces.nsILDAPAutoCompleteSession);
  1011.           }
  1012.  
  1013.           if (autocompleteDirectory && !this.mIsOffline) {
  1014.               // Add observer on the directory server we are autocompleting against
  1015.               // only if current server is different from previous.
  1016.               // Remove observer if current server is different from previous
  1017.               this.mCurrentAutocompleteDirectory = autocompleteDirectory;
  1018.               if (prevAutocompleteDirectory) {
  1019.                   if (prevAutocompleteDirectory != this.mCurrentAutocompleteDirectory) {
  1020.                       this.removeDirectorySettingsObserver(prevAutocompleteDirectory);
  1021.                       this.addDirectorySettingsObserver();
  1022.                   }
  1023.               } else {
  1024.                   this.addDirectorySettingsObserver();
  1025.               }
  1026.  
  1027.               // Fill in the session params if there is a session
  1028.               if (LDAPSession) {
  1029.                   var serverURL = Components.classes["@mozilla.org/network/ldap-url;1"]
  1030.                                   .createInstance(Components.interfaces.nsILDAPURL);
  1031.                   try {
  1032.                       serverURL.spec = this.mPrefs.getComplexValue(
  1033.                           autocompleteDirectory + ".uri",
  1034.                           Components.interfaces.nsISupportsString).data;
  1035.                   } catch (ex) {
  1036.                       dump("ERROR: " + ex + "\n");
  1037.                   }
  1038.                   LDAPSession.serverURL = serverURL;
  1039.  
  1040.                   // Get the login to authenticate as, if there is one
  1041.                   var login = "";
  1042.                   try {
  1043.                       login = this.mPrefs.getComplexValue(
  1044.                           autocompleteDirectory + ".auth.dn",
  1045.                           Components.interfaces.nsISupportsString).data;
  1046.                   } catch (ex) {
  1047.                       // If we don't have this pref, no big deal
  1048.                   }
  1049.  
  1050.                   // Set the LDAP protocol version correctly
  1051.                   var protocolVersion;
  1052.                   try {
  1053.                       protocolVersion = this.mPrefs.getCharPref(
  1054.                           autocompleteDirectory + ".protocolVersion");
  1055.                   } catch (ex) {
  1056.                       // If we don't have this pref, no big deal
  1057.                   }
  1058.                   if (protocolVersion == "2") {
  1059.                       LDAPSession.version =
  1060.                           Components.interfaces.nsILDAPConnection.VERSION2;
  1061.                   }
  1062.  
  1063.                   // Find out if we need to authenticate, and if so, tell the LDAP
  1064.                   // autocomplete session how to prompt for a password.  This window
  1065.                   // is being used to parent the authprompter.
  1066.                   LDAPSession.login = login;
  1067.                   if (login != "") {
  1068.                       var windowWatcherSvc = Components.classes[
  1069.                           "@mozilla.org/embedcomp/window-watcher;1"]
  1070.                           .getService(Components.interfaces.nsIWindowWatcher);
  1071.                       var domWin =
  1072.                           window.QueryInterface(Components.interfaces.nsIDOMWindow);
  1073.                       var authPrompter =
  1074.                           windowWatcherSvc.getNewAuthPrompter(domWin);
  1075.                       LDAPSession.authPrompter = authPrompter;
  1076.                   }
  1077.  
  1078.                   // Don't search on non-CJK strings shorter than this
  1079.                   try {
  1080.                       LDAPSession.minStringLength = this.mPrefs.getIntPref(
  1081.                           autocompleteDirectory + ".autoComplete.minStringLength");
  1082.                   } catch (ex) {
  1083.                       // if this pref isn't there, no big deal.  Just let
  1084.                       // nsLDAPAutoCompleteSession use its default.
  1085.                   }
  1086.  
  1087.                   // don't search on CJK strings shorter than this
  1088.                   try {
  1089.                       LDAPSession.cjkMinStringLength = this.mPrefs.getIntPref(
  1090.                           autocompleteDirectory + ".autoComplete.cjkMinStringLength");
  1091.                   } catch (ex) {
  1092.                       // If this pref isn't there, no big deal.  Just let
  1093.                       // nsLDAPAutoCompleteSession use its default.
  1094.                   }
  1095.  
  1096.                   // We don't try/catch here, because if this fails, we're outta luck
  1097.                   var ldapFormatter = Components.classes[
  1098.                       "@mozilla.org/ldap-autocomplete-formatter;1?type=addrbook"]
  1099.                       .createInstance().QueryInterface(
  1100.                           Components.interfaces.nsIAbLDAPAutoCompFormatter);
  1101.  
  1102.                   // Override autocomplete name format?
  1103.                   try {
  1104.                       ldapFormatter.nameFormat =
  1105.                           this.mPrefs.getComplexValue(autocompleteDirectory +
  1106.                               ".autoComplete.nameFormat",
  1107.                               Components.interfaces.nsISupportsString).data;
  1108.                   } catch (ex) {
  1109.                       // If this pref isn't there, no big deal.  Just let
  1110.                       // nsAbLDAPAutoCompFormatter use its default.
  1111.                   }
  1112.  
  1113.                   // override autocomplete mail address format?
  1114.                   try {
  1115.                       ldapFormatter.addressFormat =
  1116.                           this.mPrefs.getComplexValue(autocompleteDirectory +
  1117.                               ".autoComplete.addressFormat",
  1118.                               Components.interfaces.nsISupportsString).data;
  1119.                   } catch (ex) {
  1120.                       // If this pref isn't there, no big deal.  Just let
  1121.                       // nsAbLDAPAutoCompFormatter use its default.
  1122.                   }
  1123.  
  1124.                   try {
  1125.                       // Figure out what goes in the comment column, if anything
  1126.                       //
  1127.                       // 0 = none
  1128.                       // 1 = name of addressbook this card came from
  1129.                       // 2 = other per-addressbook format
  1130.                       var showComments = 0;
  1131.                       showComments = this.mPrefs.getIntPref(
  1132.                           "mail.autoComplete.commentColumn");
  1133.  
  1134.                       switch (showComments) {
  1135.                         case 1:
  1136.                             // Use the name of this directory
  1137.                             ldapFormatter.commentFormat = this.mPrefs.getComplexValue(
  1138.                                 autocompleteDirectory + ".description",
  1139.                                 Components.interfaces.nsISupportsString).data;
  1140.                             break;
  1141.                         case 2:
  1142.                             // Override ldap-specific autocomplete entry?
  1143.                             try {
  1144.                                 ldapFormatter.commentFormat =
  1145.                                     this.mPrefs.getComplexValue(autocompleteDirectory +
  1146.                                         ".autoComplete.commentFormat",
  1147.                                         Components.interfaces.nsISupportsString).data;
  1148.                             } catch (innerException) {
  1149.                                 // If nothing has been specified, use the ldap
  1150.                                 // organization field
  1151.                                 ldapFormatter.commentFormat = "[o]";
  1152.                             }
  1153.                             break;
  1154.                         case 0:
  1155.                         default:
  1156.                             // Do nothing
  1157.                       }
  1158.                   } catch (ex) {
  1159.                       // If something went wrong while setting up comments, try and
  1160.                       // proceed anyway
  1161.                   }
  1162.  
  1163.                   // Set the session's formatter, which also happens to
  1164.                   // force a call to the formatter's getAttributes() method
  1165.                   // -- which is why this needs to happen after we've set the
  1166.                   // various formats
  1167.                   LDAPSession.formatter = ldapFormatter;
  1168.  
  1169.                   // Override autocomplete entry formatting?
  1170.                   try {
  1171.                       LDAPSession.outputFormat =
  1172.                           this.mPrefs.getComplexValue(autocompleteDirectory +
  1173.                               ".autoComplete.outputFormat",
  1174.                               Components.interfaces.nsISupportsString).data;
  1175.                   } catch (ex) {
  1176.                       // If this pref isn't there, no big deal.  Just let
  1177.                       // nsLDAPAutoCompleteSession use its default.
  1178.                   }
  1179.  
  1180.                   // override default search filter template?
  1181.                   try {
  1182.                       LDAPSession.filterTemplate = this.mPrefs.getComplexValue(
  1183.                           autocompleteDirectory + ".autoComplete.filterTemplate",
  1184.                           Components.interfaces.nsISupportsString).data;
  1185.                   } catch (ex) {
  1186.                       // If this pref isn't there, no big deal.  Just let
  1187.                       // nsLDAPAutoCompleteSession use its default
  1188.                   }
  1189.  
  1190.                   // Override default maxHits (currently 100)
  1191.                   try {
  1192.                       LDAPSession.maxHits =
  1193.                           this.mPrefs.getIntPref(autocompleteDirectory +
  1194.                                                  ".maxHits");
  1195.                   } catch (ex) {
  1196.                       // If this pref isn't there, or is out of range, no big deal.
  1197.                       // Just let nsLDAPAutoCompleteSession use its default.
  1198.                   }
  1199.  
  1200.                   if (!this.mSessionAdded) {
  1201.                       // If we make it here, we know that session initialization has
  1202.                       // succeeded; add the session for all recipients, and
  1203.                       // remember that we've done so
  1204.                       var autoCompleteWidget;
  1205.                       for (i = 1; i <= this.mMaxAttendees; i++) {
  1206.                           autoCompleteWidget = this.getInputElement(i);
  1207.                           if (autoCompleteWidget) {
  1208.                               autoCompleteWidget.addSession(LDAPSession);
  1209.                               // Ldap searches don't insert a default entry with
  1210.                               // the default domain appended to it so reduce the
  1211.                               // minimum results for a popup to 2 in this case.
  1212.                               autoCompleteWidget.minResultsForPopup = 2;
  1213.                           }
  1214.                        }
  1215.                        this.mSessionAdded = true;
  1216.                   }
  1217.               }
  1218.           } else {
  1219.               if (this.mCurrentAutocompleteDirectory) {
  1220.                   // Remove observer on the directory server since we are not doing Ldap
  1221.                   // autocompletion.
  1222.                   this.removeDirectorySettingsObserver(this.mCurrentAutocompleteDirectory);
  1223.                   this.mCurrentAutocompleteDirectory = null;
  1224.               }
  1225.               if (this.mLDAPSession && this.mSessionAdded) {
  1226.                   for (var i = 1; i <= this.mMaxAttendees; i++)
  1227.                   this.getInputElement(i).removeSession(this.mLDAPSession);
  1228.                   this.mSessionAdded = false;
  1229.               }
  1230.           }
  1231.  
  1232.           this.mLDAPSession = LDAPSession;
  1233.         ]]></body>
  1234.       </method>
  1235.  
  1236.       <method name="addDirectoryServerObserver">
  1237.         <body><![CDATA[
  1238.           if (this.mPrefs) {
  1239.               this.mPrefs.addObserver(
  1240.                   "ldap_2.autoComplete.useDirectory",
  1241.                   this.mDirectoryServerObserver,
  1242.                   false);
  1243.               this.mPrefs.addObserver(
  1244.                   "ldap_2.autoComplete.directoryServer",
  1245.                   this.mDirectoryServerObserver,
  1246.                   false);
  1247.           }
  1248.         ]]></body>
  1249.       </method>
  1250.  
  1251.       <method name="removeDirectoryServerObserver">
  1252.         <body><![CDATA[
  1253.           if (this.mPrefs) {
  1254.               this.mPrefs.removeObserver(
  1255.                   "ldap_2.autoComplete.useDirectory",
  1256.                   this.mDirectoryServerObserver);
  1257.               this.mPrefs.removeObserver(
  1258.                   "ldap_2.autoComplete.directoryServer",
  1259.                   this.mDirectoryServerObserver);
  1260.           }
  1261.         ]]></body>
  1262.       </method>
  1263.  
  1264.       <method name="addDirectorySettingsObserver">
  1265.         <body><![CDATA[
  1266.           if (this.mPrefs) {
  1267.               this.mPrefs.addObserver(
  1268.                   this.mCurrentAutocompleteDirectory,
  1269.                   this.mDirectoryServerObserver,
  1270.                   false);
  1271.           }
  1272.         ]]></body>
  1273.       </method>
  1274.  
  1275.       <method name="removeDirectorySettingsObserver">
  1276.         <parameter name="aPrefString"/>
  1277.         <body><![CDATA[
  1278.           if (this.mPrefs) {
  1279.               this.mPrefs.removeObserver(
  1280.                   aPrefString,
  1281.                   this.mDirectoryServerObserver);
  1282.           }
  1283.         ]]></body>
  1284.       </method>
  1285.  
  1286.       <method name="releaseAutoCompleteState">
  1287.         <body><![CDATA[
  1288.           if (this.mLDAPSession && this.mSessionAdded) {
  1289.               for (i = 1; i <= this.mMaxAttendees; i++) {
  1290.                   this.getInputElement(i).removeSession(this.mLDAPSession);
  1291.               }
  1292.           }
  1293.  
  1294.           this.mSessionAdded = false;
  1295.           this.mLDAPSession = null;
  1296.         ]]></body>
  1297.       </method>
  1298.     </implementation>
  1299.  
  1300.     <handlers>
  1301.       <handler event="click" button="0"><![CDATA[
  1302.         var target = event.originalTarget;
  1303.         if (target.hasAttribute("role")) {
  1304.             if (target.hasAttribute("disabled") &&
  1305.                 target.getAttribute("disabled")) {
  1306.                 return;
  1307.             }
  1308.             var role = target.getAttribute("role");
  1309.             if (role == "CHAIR") {
  1310.                 target.setAttribute("role", "REQ-PARTICIPANT");
  1311.             } else if (role == "REQ-PARTICIPANT") {
  1312.                 target.setAttribute("role", "OPT-PARTICIPANT");
  1313.             } else if (role == "OPT-PARTICIPANT") {
  1314.                 target.setAttribute("role", "CHAIR");
  1315.             }
  1316.             return;
  1317.         }
  1318.  
  1319.         if (target.hasAttribute("status")) {
  1320.             if (target.hasAttribute("disabled") &&
  1321.                 target.getAttribute("disabled")) {
  1322.                 return;
  1323.             }
  1324.             var status = target.getAttribute("status");
  1325.             switch (status) {
  1326.                 case "NEEDS-ACTION":
  1327.                     target.setAttribute("status", "ACCEPTED");
  1328.                     break;
  1329.                 case "ACCEPTED":
  1330.                     target.setAttribute("status", "DECLINED");
  1331.                     break;
  1332.                 case "DECLINED":
  1333.                     target.setAttribute("status", "TENTATIVE");
  1334.                     break;
  1335.                 case "TENTATIVE":
  1336.                     target.setAttribute("status", "ACCEPTED");
  1337.                     break;
  1338.             }
  1339.             return;
  1340.         }
  1341.  
  1342.         if (this.mIsReadOnly || this.mIsInvitation) {
  1343.             return;
  1344.         }
  1345.  
  1346.         if (target == null ||
  1347.            (target.localName != "listboxbody" &&
  1348.             target.localName != "listcell" &&
  1349.             target.localName != "listitem")) {
  1350.             return;
  1351.         }
  1352.  
  1353.         var lastInput = this.getInputElement(this.mMaxAttendees);
  1354.         if (lastInput && lastInput.value) {
  1355.             this.appendNewRow(true);
  1356.         }
  1357.       ]]></handler>
  1358.  
  1359.       <handler event="popupshown"><![CDATA[
  1360.         this.mPopupOpen = true;
  1361.       ]]></handler>
  1362.  
  1363.       <handler event="popuphidden"><![CDATA[
  1364.         this.mPopupOpen = false;
  1365.       ]]></handler>
  1366.  
  1367.       <handler event="keydown"><![CDATA[
  1368.         if (this.mIsReadOnly || this.mIsInvitation) {
  1369.             return;
  1370.         }
  1371.         if (event.originalTarget.localName == "input") {
  1372.             switch (event.keyCode) {
  1373.                 case 46:
  1374.                 case 8:
  1375.                     if (!event.originalTarget.value) {
  1376.                         this.deleteHit(event.originalTarget);
  1377.                     }
  1378.                     event.stopPropagation();
  1379.                     break;
  1380.                 case 13:
  1381.                     this.arrowHit(event.originalTarget, 1);
  1382.                     event.stopPropagation();
  1383.                     event.preventDefault();
  1384.                     break;
  1385.             }
  1386.         }
  1387.       ]]></handler>
  1388.  
  1389.       <handler event="keypress" phase="capturing"><![CDATA[
  1390.         // In case we're currently showing the autocompletion popup
  1391.         // don't care about keypress-events and let them go. Otherwise
  1392.         // this event indicates the user wants to travel between
  1393.         // the different attendees. In this case we set the focus
  1394.         // appropriately and stop the event propagation.
  1395.         if (this.mPopupOpen || this.mIsReadOnly || this.mIsInvitation) {
  1396.             return;
  1397.         }
  1398.         if (event.originalTarget.localName == "input") {
  1399.             switch (event.keyCode) {
  1400.                 case KeyEvent.DOM_VK_UP:
  1401.                     this.arrowHit(event.originalTarget, -1);
  1402.                     event.stopPropagation();
  1403.                     break;
  1404.                 case KeyEvent.DOM_VK_DOWN:
  1405.                     this.arrowHit(event.originalTarget, 1);
  1406.                     event.stopPropagation();
  1407.                     break;
  1408.                 case KeyEvent.DOM_VK_TAB:
  1409.                     this.arrowHit(event.originalTarget, event.shiftKey ? -1 : +1);
  1410.                     event.stopPropagation();
  1411.                     event.preventDefault();
  1412.                     break;
  1413.                 case KeyEvent.DOM_VK_RETURN:
  1414.                     event.stopPropagation();
  1415.                     event.preventDefault();
  1416.                     break;
  1417.             }
  1418.         }
  1419.       ]]></handler>
  1420.  
  1421.       <handler event="input"><![CDATA[
  1422.         this.setupAutocomplete();
  1423.       ]]></handler>
  1424.     </handlers>
  1425.   </binding>
  1426.  
  1427.   <!-- the 'selection-bar' binding implements the vertical bar that provides
  1428.        a visual indication for the time range the event is configured for. -->
  1429.   <binding id="selection-bar">
  1430.     <content>
  1431.       <xul:scrollbox anonid="scrollbox" width="0" orient="horizontal" flex="1">
  1432.         <xul:box class="selection-bar" anonid="selection-bar">
  1433.           <xul:box class="selection-bar-left" anonid="leftbox"/>
  1434.           <xul:spacer class="selection-bar-spacer" flex="1"/>
  1435.           <xul:box class="selection-bar-right" anonid="rightbox"/>
  1436.         </xul:box>
  1437.       </xul:scrollbox>
  1438.     </content>
  1439.  
  1440.     <implementation>
  1441.       <field name="mRange">0</field>
  1442.       <field name="mStartHour">0</field>
  1443.       <field name="mEndHour">24</field>
  1444.       <field name="mContentWidth">0</field>
  1445.       <field name="mHeaderHeight">0</field>
  1446.       <field name="mRatio">0</field>
  1447.       <field name="mBaseDate">null</field>
  1448.       <field name="mStartDate">null</field>
  1449.       <field name="mEndDate">null</field>
  1450.       <field name="mMouseX">0</field>
  1451.       <field name="mMouseY">0</field>
  1452.       <field name="mDragState">0</field>
  1453.       <field name="mMargin">0</field>
  1454.       <field name="mWidth">0</field>
  1455.       <field name="mForce24Hours">false</field>
  1456.       <field name="mZoomFactor">100</field>
  1457.       <!-- constant that defines at which ratio an event is clipped, when moved or resized -->
  1458.       <field name="mfClipRatio">0.7</field>
  1459.       <field name="mLeftBox"/>
  1460.       <field name="mRightBox"/>
  1461.       <field name="mSelectionbar"/>
  1462.  
  1463.       <property name="zoomFactor">
  1464.         <getter><![CDATA[
  1465.           return this.mZoomFactor;
  1466.         ]]></getter>
  1467.         <setter><![CDATA[
  1468.           this.mZoomFactor = val;
  1469.           return val;
  1470.         ]]></setter>
  1471.       </property>
  1472.  
  1473.       <property name="force24Hours">
  1474.         <getter><![CDATA[
  1475.           return this.mForce24Hours;
  1476.         ]]></getter>
  1477.         <setter><![CDATA[
  1478.           this.mForce24Hours = val;
  1479.           this.initTimeRange();
  1480.           this.update();
  1481.           return val;
  1482.         ]]></setter>
  1483.       </property>
  1484.  
  1485.       <property name="ratio">
  1486.         <setter><![CDATA[
  1487.           this.mRatio = val;
  1488.           this.update();
  1489.           return val;
  1490.         ]]></setter>
  1491.       </property>
  1492.  
  1493.       <constructor><![CDATA[
  1494.         this.initTimeRange();
  1495.  
  1496.         // The basedate is the date/time from which the display
  1497.         // of the timebar starts. The range is the number of days
  1498.         // we should be able to show. the start- and enddate
  1499.         // is the time the event is scheduled for.
  1500.         this.mRange = Number(this.getAttribute("range"));
  1501.         this.mSelectionbar =
  1502.             document.getAnonymousElementByAttribute(
  1503.                 this, "anonid", "selection-bar");
  1504.       ]]></constructor>
  1505.  
  1506.       <property name="baseDate">
  1507.         <setter><![CDATA[
  1508.           // we need to convert the date/time in question in
  1509.           // order to calculate with hours that are aligned
  1510.           // with our timebar display.
  1511.           var kDefaultTimezone = calendarDefaultTimezone();
  1512.           this.mBaseDate = val.getInTimezone(kDefaultTimezone);
  1513.           this.mBaseDate.isDate = true;
  1514.           this.mBaseDate.makeImmutable();
  1515.           return val;
  1516.         ]]></setter>
  1517.       </property>
  1518.  
  1519.       <property name="startDate">
  1520.         <setter><![CDATA[
  1521.           // currently we *always* set the basedate to be
  1522.           // equal to the startdate. we'll most probably
  1523.           // want to change this later.
  1524.           this.baseDate = val;
  1525.           // we need to convert the date/time in question in
  1526.           // order to calculate with hours that are aligned
  1527.           // with our timebar display.
  1528.           var kDefaultTimezone = calendarDefaultTimezone();
  1529.           this.mStartDate = val.getInTimezone(kDefaultTimezone);
  1530.           this.mStartDate.makeImmutable();
  1531.           return val;
  1532.         ]]></setter>
  1533.         <getter><![CDATA[
  1534.           return this.mStartDate;
  1535.         ]]></getter>
  1536.       </property>
  1537.  
  1538.       <property name="endDate">
  1539.         <setter><![CDATA[
  1540.           // we need to convert the date/time in question in
  1541.           // order to calculate with hours that are aligned
  1542.           // with our timebar display.
  1543.           var kDefaultTimezone = calendarDefaultTimezone();
  1544.           this.mEndDate = val.getInTimezone(kDefaultTimezone);
  1545.           if (this.mEndDate.isDate) {
  1546.               this.mEndDate.day += 1;
  1547.           }
  1548.           this.mEndDate.makeImmutable();
  1549.           return val;
  1550.         ]]></setter>
  1551.         <getter><![CDATA[
  1552.           return this.mEndDate;
  1553.         ]]></getter>
  1554.       </property>
  1555.  
  1556.       <property name="leftdragWidth">
  1557.         <getter><![CDATA[
  1558.           if (!this.mLeftBox) {
  1559.             this.mLeftBox =
  1560.                 document.getAnonymousElementByAttribute(
  1561.                     this, "anonid", "leftbox");
  1562.           }
  1563.           return this.mLeftBox.boxObject.width;
  1564.         ]]></getter>
  1565.       </property>
  1566.       <property name="rightdragWidth">
  1567.         <getter><![CDATA[
  1568.           if (!this.mRightBox) {
  1569.               this.mRightBox =
  1570.                   document.getAnonymousElementByAttribute(
  1571.                       this, "anonid", "rightbox");
  1572.           }
  1573.           return this.mRightBox.boxObject.width;
  1574.         ]]></getter>
  1575.       </property>
  1576.  
  1577.       <method name="init">
  1578.         <parameter name="width"/>
  1579.         <parameter name="height"/>
  1580.         <body><![CDATA[
  1581.           this.mContentWidth = width;
  1582.           this.mHeaderHeight = height + 2;
  1583.           this.mMargin = 0;
  1584.           this.update();
  1585.         ]]></body>
  1586.       </method>
  1587.  
  1588.       <!-- given some specific date this method calculates the
  1589.            corrposonding offset in fractional hours -->
  1590.       <method name="date2offset">
  1591.         <parameter name="date"/>
  1592.         <body><![CDATA[
  1593.           var num_hours = this.mEndHour - this.mStartHour;
  1594.           var diff = date.subtractDate(this.mBaseDate);
  1595.           var offset = diff.days * num_hours;
  1596.           var hours = (diff.hours - this.mStartHour) + (diff.minutes / 60.0);
  1597.           if (hours < 0) {
  1598.               hours = 0;
  1599.           }
  1600.           if (hours > num_hours) {
  1601.               hours = num_hours;
  1602.           }
  1603.           offset += hours;
  1604.           return offset;
  1605.         ]]></body>
  1606.       </method>
  1607.  
  1608.       <method name="update">
  1609.         <body><![CDATA[
  1610.           if (!this.mStartDate || !this.mEndDate) {
  1611.               return;
  1612.           }
  1613.  
  1614.           // Calculate the relation of startdate/basedate and enddate/startdate.
  1615.           var offset = this.mStartDate.subtractDate(this.mBaseDate);
  1616.           var duration = this.mEndDate.subtractDate(this.mStartDate);
  1617.  
  1618.           // Calculate how much pixels a single hour and a single day take up.
  1619.           var num_hours = this.mEndHour - this.mStartHour;
  1620.           var hour_width = this.mContentWidth / num_hours;
  1621.  
  1622.           // Calculate the offset in fractional hours that corrospond
  1623.           // to our start- and end-time.
  1624.           var start_offset_in_hours = this.date2offset(this.mStartDate);
  1625.           var end_offset_in_hours = this.date2offset(this.mEndDate);
  1626.           var duration_in_hours = end_offset_in_hours - start_offset_in_hours;
  1627.  
  1628.           // Calculate width & margin for the selection bar based on the
  1629.           // relation of startdate/basedate and enddate/startdate.
  1630.           // This is a simple conversion from hours to pixels.
  1631.           this.mWidth = duration_in_hours * hour_width;
  1632.           var totaldragwidths = this.leftdragWidth + this.rightdragWidth;
  1633.           if (this.mWidth < totaldragwidths) {
  1634.               this.mWidth = totaldragwidths;
  1635.           }
  1636.           this.mMargin = start_offset_in_hours * hour_width;
  1637.  
  1638.           // Calculate the difference between content and container in pixels.
  1639.           // The container is the window showing this control, the content is the
  1640.           // total number of pixels the selection bar can theoretically take up.
  1641.           var total_width = this.mContentWidth * this.mRange - this.parentNode.boxObject.width;
  1642.  
  1643.           // Calculate the current scroll offset.
  1644.           var offset = Math.floor(total_width * this.mRatio);
  1645.  
  1646.           // The final margin is the difference between the date-based margin
  1647.           // and the scroll-based margin.
  1648.           this.mMargin -= offset;
  1649.  
  1650.           // Set the styles based on the calculations above for the 'selection-bar'.
  1651.           var style = "width: " + this.mWidth +
  1652.                       "px; margin-left: " + this.mMargin +
  1653.                       "px; margin-top: " + this.mHeaderHeight + "px;";
  1654.           this.mSelectionbar.setAttribute("style", style);
  1655.  
  1656.           var event = document.createEvent('Events');
  1657.           event.initEvent('timechange', true, false);
  1658.           event.startDate = this.mStartDate;
  1659.           event.endDate = this.mEndDate.clone();
  1660.           if (event.endDate.isDate) {
  1661.               event.endDate.day--;
  1662.           }
  1663.           event.endDate.makeImmutable();
  1664.           this.dispatchEvent(event);
  1665.         ]]></body>
  1666.       </method>
  1667.  
  1668.       <method name="setWidth">
  1669.         <parameter name="width"/>
  1670.         <body><![CDATA[
  1671.             var scrollbox =
  1672.                 document.getAnonymousElementByAttribute(
  1673.                     this, "anonid", "scrollbox");
  1674.             scrollbox.setAttribute("width", width);
  1675.         ]]></body>
  1676.       </method>
  1677.  
  1678.       <method name="initTimeRange">
  1679.         <body><![CDATA[
  1680.           if (this.force24Hours) {
  1681.               this.mStartHour = 0;
  1682.               this.mEndHour = 24;
  1683.           } else {
  1684.               this.mStartHour = getPrefSafe("calendar.view.daystarthour", 8);
  1685.               this.mEndHour = getPrefSafe("calendar.view.dayendhour", 19);
  1686.           }
  1687.         ]]></body>
  1688.       </method>
  1689.  
  1690.       <method name="moveTime">
  1691.         <parameter name="time"/>
  1692.         <parameter name="delta"/>
  1693.         <parameter name="doclip"/>
  1694.         <body><![CDATA[
  1695.           var newTime = time.clone();
  1696.           var clip_minutes = 60 * this.zoomFactor / 100;
  1697.           if (newTime.isDate) {
  1698.               clip_minutes = 60 * 24;
  1699.           }
  1700.           var num_hours = this.mEndHour - this.mStartHour;
  1701.           var hour_width = this.mContentWidth / num_hours;
  1702.           var minutes_per_pixel = 60 / hour_width;
  1703.           var minute_shift = minutes_per_pixel * delta;
  1704.           var isClipped = Math.abs(minute_shift) >= (this.mfClipRatio * clip_minutes);
  1705.           if (isClipped) {
  1706.               if (delta > 0) {
  1707.                   if (time.isDate) {
  1708.                       newTime.day++;
  1709.                   } else {
  1710.                       if (doclip) {
  1711.                           newTime.minute -= newTime.minute % clip_minutes;
  1712.                       }
  1713.                       newTime.minute += clip_minutes;
  1714.                   }
  1715.               } else if (delta < 0) {
  1716.                   if (time.isDate) {
  1717.                       newTime.day--;
  1718.                   } else {
  1719.                       if (doclip) {
  1720.                           newTime.minute -= newTime.minute % clip_minutes;
  1721.                       }
  1722.                       newTime.minute -= clip_minutes;
  1723.                   }
  1724.               }
  1725.           }
  1726.  
  1727.           if (!newTime.isDate) {
  1728.               if (newTime.hour < this.mStartHour) {
  1729.                   newTime.hour = this.mEndHour - 1;
  1730.                   newTime.day--;
  1731.               }
  1732.               if (newTime.hour >= this.mEndHour) {
  1733.                   newTime.hour = this.mStartHour;
  1734.                   newTime.day++;
  1735.               }
  1736.           }
  1737.  
  1738.           return newTime;
  1739.         ]]></body>
  1740.       </method>
  1741.     </implementation>
  1742.  
  1743.     <handlers>
  1744.       <handler event="mousedown"><![CDATA[
  1745.         var element = event.target;
  1746.         this.mMouseX = event.screenX;
  1747.         var mouseX = event.clientX - element.boxObject.x;
  1748.  
  1749.         if (mouseX >= this.mMargin) {
  1750.             if (mouseX <= (this.mMargin + this.mWidth)) {
  1751.                 if (mouseX <= (this.mMargin + this.leftdragWidth)) {
  1752.                     // Move the startdate only...
  1753.                     window.setCursor("w-resize");
  1754.                     this.mDragState = 2;
  1755.                 } else if (mouseX >= (this.mMargin + this.mWidth - (this.rightdragWidth))) {
  1756.                     // Move the enddate only..
  1757.                     window.setCursor("e-resize");
  1758.                     this.mDragState = 3;
  1759.                 } else {
  1760.                     // Move the startdate and the enddate
  1761.                     this.mDragState = 1;
  1762.                     window.setCursor("-moz-grab");
  1763.                 }
  1764.             }
  1765.         }
  1766.       ]]></handler>
  1767.  
  1768.       <handler event="mousemove"><![CDATA[
  1769.         var mouseX = event.screenX;
  1770.         if (this.mDragState == 1) {
  1771.             // Move the startdate and the enddate
  1772.             var delta = mouseX - this.mMouseX;
  1773.             var newStart = this.moveTime(this.mStartDate, delta, false);
  1774.             if (newStart.compare(this.mStartDate) != 0) {
  1775.                 newEnd = this.moveTime(this.mEndDate, delta, false);
  1776.  
  1777.                 // We need to adapt this date in case we're dealing with
  1778.                 // an all-day event. This is because setting 'endDate' will
  1779.                 // automatically add one day extra for all-day events.
  1780.                 if (newEnd.isDate) {
  1781.                     newEnd.day--;
  1782.                 }
  1783.  
  1784.                 this.startDate = newStart;
  1785.                 this.endDate = newEnd;
  1786.                 this.mMouseX = mouseX;
  1787.                 this.update();
  1788.             }
  1789.         } else if (this.mDragState == 2) {
  1790.             // Move the startdate only...
  1791.             var delta = event.screenX - this.mSelectionbar.boxObject.screenX;
  1792.             var newStart = this.moveTime(this.mStartDate, delta, true);
  1793.             if (newStart.compare(this.mEndDate) >= 0) {
  1794.                 if (!this.mStartDate.isDate) {
  1795.                     newStart = this.mEndDate;
  1796.                 }
  1797.                 else{
  1798.                     return;
  1799.                 }
  1800.             }
  1801.             if (newStart.compare(this.mStartDate) != 0) {
  1802.                 this.startDate = newStart;
  1803.                 this.update();
  1804.             }
  1805.         } else if (this.mDragState == 3) {
  1806.             // Move the enddate only..
  1807.             var delta = mouseX - (this.mSelectionbar.boxObject.screenX +
  1808.                                   this.mSelectionbar.boxObject.width);
  1809.             var newEnd = this.moveTime(this.mEndDate, delta, true);
  1810.             if (newEnd.compare(this.mStartDate) < 0) {
  1811.                 newEnd = this.mStartDate;
  1812.             }
  1813.             if (newEnd.compare(this.mEndDate) != 0) {
  1814.                 // We need to adapt this date in case we're dealing with
  1815.                 // an all-day event. This is because setting 'endDate' will
  1816.                 // automatically add one day extra for all-day events.
  1817.                 if (newEnd.isDate) {
  1818.                     newEnd.day--;
  1819.                 }
  1820.  
  1821.                 // Don't allow all-day events to be shorter than a single day.
  1822.                 if (!newEnd.isDate || (newEnd.compare(this.startDate) >= 0)) {
  1823.                     this.endDate = newEnd;
  1824.                     this.update();
  1825.                 }
  1826.             }
  1827.         }
  1828.       ]]></handler>
  1829.  
  1830.       <handler event="mouseup"><![CDATA[
  1831.         this.mDragState = 0;
  1832.         window.setCursor("auto");
  1833.       ]]></handler>
  1834.     </handlers>
  1835.   </binding>
  1836.  
  1837. </bindings>
  1838.